package org.os.framework.crypto.algorithm;

import org.bouncycastle.util.encoders.Hex;
import org.os.framework.crypto.model.Crypto;

import cn.hutool.core.lang.Assert;

import java.math.BigInteger;
import java.util.Random;

/**
 * Paillier算法
 *
 * @author 魏祖潇
 */
public class Paillier implements Crypto<BigInteger> {

    public static Builder builder() {
        return new Builder();
    }

    public static class Builder {
        private String privateKey;
        private String publicKey;

        public Builder privateKey(String privateKey) {
            this.privateKey = privateKey;
            return this;
        }

        public Builder publicKey(String publicKey) {
            this.publicKey = publicKey;
            return this;
        }

        public Paillier build() {
            return new Paillier(this.privateKey, this.publicKey);
        }
    }

    private final int modules = 512;
    private final BigInteger G = BigInteger.valueOf(2);
    private final BigInteger bigInteger = new BigInteger(String.valueOf(Integer.MAX_VALUE));

    private byte[] privateKey;
    private byte[] publicKey;

    private Paillier(String privateKey, String publicKey) {
        if (null != privateKey) {
            privateKey(privateKey);
        }
        if (null != publicKey) {
            publicKey(publicKey);
        }
    }

    private void privateKey(String privateKey) {
        Assert.notNull(privateKey, "请输入私钥");
        this.privateKey = Hex.decode(privateKey);
    }

    private void publicKey(String publicKey) {
        Assert.notNull(publicKey, "请输入公钥");
        this.publicKey = Hex.decode(publicKey);
    }

    @Override
    public BigInteger encrypt(BigInteger original) {
        BigInteger n = new BigInteger(publicKey);
        BigInteger n2 = n.multiply(n);
        BigInteger r = new BigInteger(modules, new Random());
        return G.modPow(original, n.multiply(n2)).multiply(r.modPow(n, n2)).mod(n2);
    }

    @Override
    public BigInteger decrypt(BigInteger encrypted) {
        // 结果若为负数
        BigInteger big = encrypt(bigInteger);
        encrypted = plus(encrypted, big);
        BigInteger n = new BigInteger(publicKey);
        BigInteger lambda = new BigInteger(privateKey);
        BigInteger n2 = n.multiply(n);
        BigInteger u = G.modPow(lambda, n2).subtract(BigInteger.ONE).divide(n).modInverse(n);
        BigInteger r = encrypted.modPow(lambda, n2).subtract(BigInteger.ONE).divide(n).multiply(u).mod(n);
        return r.subtract(bigInteger);
    }

    public BigInteger plus(BigInteger a, BigInteger b) {
        BigInteger n = new BigInteger(publicKey);
        BigInteger n2 = n.multiply(n);
        return a.multiply(b).mod(n2);
    }

    public BigInteger multiply(BigInteger a, BigInteger multiple) {
        BigInteger n = new BigInteger(publicKey);
        BigInteger n2 = n.multiply(n);
        return a.modPow(multiple, n2);
    }

}
